#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>

#include <linux/vmalloc.h>
#include <linux/uaccess.h>
#include <linux/poll.h>
#include <linux/mutex.h>
#include <linux/spinlock.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>

#include "char_driver.h"

#define INFO(fmt,...) printk("[char driver]: "fmt,##__VA_ARGS__)
#define CHAR_COUNT 1
#define BUF_LEN 20

int major=88;
int minor=0;
int count=CHAR_COUNT;

#if 0
/*0.[可选] 设计struct cdev子类*/
struct char_cdev
{
    struct cdev cdev;
    char buf[BUF_LEN];
    int curlen;

    atomic_t atomic;
/*原子操作*/
    struct mutex mutex_lock;
/*
#include <linux/mutex.h>
互斥锁初始化
mutex_init
上锁(上锁失败，阻塞)
mutex_lock
尝试上锁(上锁失败返回错误)
mutex_trylock
解锁
mutex_unlock
*/
    spinlock_t lock;
/*
#include <linux/spinlock.h>
互斥锁初始化
spin_lock_init
上锁(上锁失败，阻塞)
spin_lock
尝试上锁(上锁失败返回错误)
spin_trylock
解锁
spin_unlock
*/
    struct semaphore sem;
/*

初始化
sema_init
减操作,不可被中断
down
加操作
up
down_trylock
减操作，可被其他系统信号中断，例如Ctrl+C
down_interruptible
*/
    wait_queue_head_t rq;
    wait_queue_head_t wq;
};
/*1.创建struct cdev对象或者其子对象,两种方式
    1>定义全局变量
    2>为该对象动态分配内存进空间
*/
struct char_cdev char_cdev;
#else
/*0.[可选] 设计struct cdev子类*/
struct char_cdev
{
    struct device* dev;
    struct cdev cdev;
    char buf[BUF_LEN];
    int curlen;

    atomic_t atomic;
    struct mutex mutex_lock;
    spinlock_t lock;
    struct semaphore sem;
    wait_queue_head_t rq;
    wait_queue_head_t wq;
};
#endif

struct class* pclass = NULL;

int char_open(struct inode* pnode, struct file* pfile)
{
    struct char_cdev* pcdev = NULL;
    pfile->private_data = container_of(pnode->i_cdev,struct char_cdev,cdev);
    pcdev = (struct char_cdev*)pfile->private_data;
    if(!atomic_dec_and_test(&pcdev->atomic)){
        atomic_inc(&pcdev->atomic);
        INFO("Can not open this device\n");
        return -1;
    }
    INFO("char_open success\n");
    return 0;
}

int char_close(struct inode* pnode, struct file* pfile)
{
    struct char_cdev* pcdev = (struct char_cdev*)pfile->private_data;
    atomic_inc(&pcdev->atomic);
    INFO("char_close success\n");
    return 0;
}

ssize_t char_read(struct file* pfile, char __user* puser,size_t count, loff_t *ppos)
{
    struct char_cdev* pcdev = (struct char_cdev*)pfile->private_data;
    int ret = 0;

    down(&pcdev->sem);
    if(pcdev->curlen < 0){
        if(pfile->f_flags & O_NONBLOCK){
            up(&pcdev->sem);
            INFO("char_read is no date(O_NONBLOCK)\n");
            return -1;
        }

        up(&pcdev->sem);
        ret = wait_event_interruptible(pcdev->rq,pcdev->curlen > 0);
        if(ret){
            INFO("char_read is wake up for signal\n");
            return -ERESTARTSYS;
        }
    }

    if(count > pcdev->curlen){
        count = pcdev->curlen;
    }

    ret = copy_to_user(puser, pcdev->buf, count);
    if(ret){
        INFO("copy_to_user failed\n");
        return -1;
    }

    memcpy(pcdev->buf, pcdev->buf+count, pcdev->curlen-count);

    pcdev->curlen = pcdev->curlen - count;
    up(&pcdev->sem);
    wake_up(&pcdev->wq);

    return count;
}

ssize_t char_write(struct file* pfile, const char __user* puser,size_t count, loff_t *ppos)
{
    struct char_cdev* pcdev = (struct char_cdev*)pfile->private_data;
    int ret = 0;

    down(&pcdev->sem);
    if(pcdev->curlen >= BUF_LEN ){
        if(pfile->f_flags & O_NONBLOCK){
            up(&pcdev->sem);
            INFO("char_write is no date for buf of full(O_NONBLOCK)\n");
            return -1;
        }

        up(&pcdev->sem);
        ret = wait_event_interruptible(pcdev->wq,pcdev->curlen > BUF_LEN);
        if(ret){
            INFO("char_write is wake up for signal\n");
            return -ERESTARTSYS;
        }
    }

    if(count > BUF_LEN-pcdev->curlen){
        count = BUF_LEN-pcdev->curlen;
    }

    ret = copy_from_user(pcdev->buf+pcdev->curlen, puser, count);
    if(ret){
        INFO("copy_from_user failed\n");
        return -1;
    }

    pcdev->curlen = pcdev->curlen + count;
    up(&pcdev->sem);
    wake_up(&pcdev->rq);

    return count;
}

long char_ioctl(struct file* pfile, unsigned int cmd, unsigned long arg)
{
    struct char_cdev* pcdev = (struct char_cdev*)pfile->private_data;
    int ret = 0;
    int buf_len = BUF_LEN;
    int __user* puser = (int __user*)arg;

    switch(cmd){
        case GET_BUF_LEN:
            ret = copy_to_user(puser, &buf_len, sizeof(int));
            if(ret){
                INFO("GET_BUF_LEN copy_from_user failed\n");
                return -1;
            }
            break;
        case GET_CUR_LEN:
            ret = copy_to_user(puser, &pcdev->curlen, sizeof(int));
            if(ret){
                INFO("GET_CUR_LEN copy_from_user failed\n");
                return -1;
            }
            break;
        case CLEAR_BUF:
            memset(pcdev->buf,0,BUF_LEN);
            pcdev->curlen = 0;
            break;
        default:
            INFO("no char_ioctl cmd\n");
    }

    return 0;
}

unsigned int char_poll(struct file* pfile,poll_table* ptable)
{
    struct char_cdev* pcdev = (struct char_cdev*)pfile->private_data;
    unsigned int mask = 0;

    poll_wait(pfile, &pcdev->rq, ptable);
    poll_wait(pfile, &pcdev->wq, ptable);

    down(&pcdev->sem);
    if(pcdev->curlen > 0){
        mask |= POLLIN | POLLRDNORM;
    }

    if(pcdev->curlen < BUF_LEN){
        mask |= POLLOUT | POLLRDNORM;
    }
    up(&pcdev->sem);

    return mask;
}

struct file_operations char_devops = {
    .owner = THIS_MODULE,
    .open = char_open,
    .release = char_close,
    .read = char_read,
    .write = char_write,
    .unlocked_ioctl = char_ioctl,
    .poll = char_poll,
};

#if 0
int __init char_init(void)
{
    int ret = 0;
    dev_t devID = MKDEV(major,minor);

/*2.申请设备号*/
    if(major){
        ret = register_chrdev_region(devID,count,"char_cdev");
        if(ret){
            INFO("char_cdev register_chrdev_region failed\n");
            ret = alloc_chrdev_region(&devID,minor,count,"char_cdev");
            if(ret){
                INFO("char_cdev alloc_chrdev_region failed\n");
                return -1;
            }
            major = MAJOR(devID);
        }
    }else{
        ret = alloc_chrdev_region(&devID,minor,count,"char_cdev");
        if(ret){
            INFO("char_cdev alloc_chrdev_region failed\n");
            return -1;
        }
        major = MAJOR(devID);
    }

    INFO("char_cdev dev_t=%d\n",devID);

    pclass = class_create(THIS_MODULE,"cdev_class");

/*3.填充struct cdev对象*/
    cdev_init(&char_cdev.cdev,&char_devops);
    char_cdev.cdev.owner = THIS_MODULE;

/*4.将struct cdev对象插入到内核管理此类对象的链式数据结中,并为其指定设备号*/
    cdev_add(&char_cdev.cdev,devID,count);

    char_cdev.curlen = 0;
    atomic_set(&char_cdev.atomic, 1);
    init_waitqueue_head(&char_cdev.rq);
    init_waitqueue_head(&char_cdev.wq);
    sema_init(&char_cdev.sem,1);
    mutex_init(&char_cdev.mutex_lock);
    spin_lock_init(&char_cdev.lock);

    device_create(pclass, NULL, devID, NULL, "char_cdev");
/*request_irq函数的调用必须位于入口函数的最后*/

    return 0;
}

void __exit char_exit(void)
{
/*1.注销设备号*/
    dev_t devID = MKDEV(major,minor);
    unregister_chrdev_region(devID,count);

/*2.从管理strucr cdev对象的链式数据结构中移除struct cdev对象;如果strucr cdev对象或其子对象是动态创建的,则释放其占用的内存空间*/
    cdev_del(&char_cdev.cdev);
    mutex_destroy(&char_cdev.mutex_lock);
    device_destroy(pclass, devID);
    class_destroy(pclass);
    pclass = NULL;
}
#else

static int char_probe(struct platform_device* pdev)
{
    int ret = 0;
    dev_t devID = MKDEV(major,minor);
    struct char_cdev *pcdev = (struct char_cdev*)vzalloc(sizeof(struct char_cdev));
    if(pcdev==NULL){
        ret = -ENOMEM;
        goto ERR_NOMEM;
    }

/*2.申请设备号*/
    ret = register_chrdev_region(devID,count,"char_cdev");
    if(ret){
        INFO("char_cdev register_chrdev_region failed\n");
        ret = alloc_chrdev_region(&devID,minor,count,"char_cdev");
        if(ret){
            INFO("char_cdev alloc_chrdev_region failed\n");
            ret = -EINVAL;
            goto ERR_ALLOC;
        }
        major = MAJOR(devID);
    }

    pclass = class_create(THIS_MODULE, "cdev_class");
    
    platform_set_drvdata(pdev,pcdev);/*pdev->dev.driver_data = pcdev;*/
    pcdev->dev = &pdev->dev;
    pcdev->curlen = 0;

/*3.填充struct cdev对象*/
    cdev_init(&pcdev->cdev,&char_devops);
    pcdev->cdev.owner = THIS_MODULE;

/*4.将struct cdev对象插入到内核管理此类对象的链式数据结中,并为其指定设备号*/
    cdev_add(&pcdev->cdev,devID,count);

    atomic_set(&pcdev->atomic, 1);
    init_waitqueue_head(&pcdev->rq);
    init_waitqueue_head(&pcdev->wq);
    sema_init(&pcdev->sem,1);
    mutex_init(&pcdev->mutex_lock);
    spin_lock_init(&pcdev->lock);

    device_create(pclass, NULL, devID, NULL, "char_cdev");
/*request_irq函数的调用必须位于入口函数的最后*/   

    return 0;
ERR_ALLOC:
    vfree(pcdev);
ERR_NOMEM:
    return ret;
}

static int char_remove(struct platform_device* pdev)
{
    dev_t devID = 0;
    struct char_cdev* pcdev = (struct char_cdev*)platform_get_drvdata(pdev);
    if(pcdev == NULL){
        return -ENODEV;
    }
/*1.注销设备号*/
    devID = MKDEV(major,minor);
    unregister_chrdev_region(devID,count);

/*2.从管理strucr cdev对象的链式数据结构中移除struct cdev对象;如果strucr cdev对象或其子对象是动态创建的,则释放其占用的内存空间*/
    cdev_del(&pcdev->cdev);

    mutex_destroy(&pcdev->mutex_lock);
    device_destroy(pclass, devID);
    class_destroy(pclass);
    pclass = NULL;
    vfree(pcdev);/*vfree(pdev->dev.driver_data);*/
    pcdev = NULL;
    return 0;
}

static const struct of_device_id char_match_table[] = {
    {.compatible = "char_device"},
    {},
};

static const struct platform_device_id char_id_table[] = {
    {.name = "char0"},
    {.name = "char1"},
    {.name = "char2"},
    {},
};

static struct platform_driver char_driver = {
    .probe = char_probe,
    .remove = char_remove,
    .driver = {
        .name = "char_driver",
        .owner = THIS_MODULE,
        .of_match_table = char_match_table,
    },
    .id_table = char_id_table,
};
#endif

#if 0
module_init(char_init);
module_exit(char_exit);
#else
module_platform_driver(char_driver);
/*
int __init char_init(void)
{
    return platform_driver_register(&char_driver);
}

void __exit char_exit(void)
{
    platform_driver_unregister(&char_driver);
}

module_init(char_init);
module_exit(char_exit);
*/
#endif

/*[必须]开源声明*/
MODULE_LICENSE("GPL");
/*作者*/
MODULE_AUTHOR("Liu");
/*描述*/
MODULE_DESCRIPTION("char_driver common");